Домашнее задание №2 - Линейные модели. Градиентный спуск

В этом домашнем задании мы с вами научимся обучать линейные модели регрессии и классификации при помощи очень мощного, но в то же время довольно понятного алгоритма, который называется градиентный спуск. Помимо линейных моделей он используется и для обучения самых сложных нейронных сетей! Также мы потренируемся применять готовые реализации линейных моделей для задач регрессии и бинарной классификации.

Маленькое теоретическое отступление

Основное свойство антиградиента (-1 * градиент) – он указывает в сторону наискорейшего убывания функции в данной точке. Соответственно, будет логично стартовать из некоторой точки, сдвинуться в сторону антиградиента, пересчитать антиградиент и снова сдвинуться в его сторону и т.д. Запишем это более формально.

Пусть $w_0$ – начальный набор параметров (коэффициентов линейной модели) ((например, нулевой или сгенерированный из некоторого, случайного распределения)). Тогда обычный градиентный спуск состоит в повторении следующих шагов до сходимости:

$$ w_{k + 1} = w_{k} - \eta \nabla_{w} Q(w_{k}), $$

где $\nabla_{w} Q(w_{k})$ – градиент функции потерь в точке $w_k$, а $\eta$ – скорость обучения (learning rate).

Градиентный спуск обычно останавливают, когда прошло заданное максимальное количество итераций или когда графиент близок к нулю (т.е. наши параметры практически не меняются). Для реализации второго варианта считают норму градиента (по сути длину вектора). Это можно сделать несколькими способами:

$$ l1_{norm} = \sum{|w_i|} $$$$ l2_{norm} = \sum{(w_i)^{2}} $$

Попробуем разобраться на простом примере. Рассмотрим функцию от двух переменных: $f(x, y) = \sin^2 x + \sin^2 y$

Обратите внимание, что $x$ - numpy-array вектор длины 2.

Reminder:
Что мы хотим? Мы хотим найти минимум этой функции (в машинном обучении мы обычно хотим найти минимум функции потерь, например, MSE), а точнее найти $w_1$ и $w_2$ такие, что при них значение $f(w_1, w_2)$ минимально, то есть точку экстремума.

Как мы будем искать эту точку? Используем методы оптимизации (в нашем случае - минимизации). Одним из таких методов и является градиентный спуск.

Задание 1. Градиентный спуск для функции $f$ (1 балл)

Реализуйте функцию, которая будет осуществлять градиентный спуск для функции $f$:

Примечание: Вам нужно посчитать частные производные именно аналитически и переписать их в код, а не считать производные численно (через отношение приращения функции к приращению аргумента) -- в этих двух случаях могут различаться ответы, поэтому будьте внимательны.

Проверим, что градиент принимает вектор из двух чисел и выдает на этой точке верное значение

Визуализируем точки градиентного спуска на 3D-графике нашей функции. Звездочками будут обозначены точки (тройки $w_1, w_2, f(w_1, w_2)$), по которым Ваш алгоритм градиентного спуска двигался к минимуму (Для того, чтобы написовать этот график, мы и сохраняли значения $cur\_w_1, cur\_w_2, f(cur\_w_1, cur\_w_2)$ в steps в процессе спуска).

Если у Вас правильно написана функция grad_descent_2d, то звездочки на картинке должны сходиться к одной из точек минимума функции. Вы можете менять начальные приближения алгоритма, значения lr и num_iter и получать разные результаты.

We can see that the stars are indeed closer and closer to the mininmum!

Посмотрим на зависимость значения функции от шага градиентного спуска.

Задание 2. Реализация линейной регресии (суммарно 9 баллов)

Так как мы будем использовать градиентный спуск для обучения модели, важной частью является реализация функции потерь и функции для расчета ее градиента. Перем началом стоит напомнить, как считать градиент MSE. Вывод этой формулы можно найти здесь

$$ MSE = \frac{1}{N}\sum(y_{true} - y_{pred}) ^ 2 $$$$ \nabla{MSE} = \frac{2}{N} X^T (y_{pred} - y_{true}) $$

Здесь имеется в виду именно матричное умножение.

Задание 2.1. MSE и ее градиент (2 балла)

Мы будем использовать следующий класс для расчета градиента наших функций потерь:

В данном задании нужно будет реализовать линейную регрессию и обучить ее при помощи градиентного спуска. Для этого нужно будет заполнять пропуски кода в соответствующих классах. Для начала мы реализуем базовый класс для всех линейных моделей, от которого потом будем наследоваться при реализации линейной и логистической регресий. Не переживайте, этот класс уже реализован, вам достостаточно просто разобраться с кодом.

Задание 2.2. Предсказания линейной регрессии (3 балла)

Реализуйте метод predict у класса CustomLinearRegression, не забудьте про свободный член!

Проверим нашу реализацию на простом примере

Задание 2.3. Используем встроенную линейную регрессию (4 балла)

Поработаем с данными о ценах на дома в Бостоне. Постройте модель линейной регресии при помощи LinearRegression из sklearn. Не забудьте разделить данные на тренировочную и тестовую части, а также правильно предобработать признаки. В конце воспользуйтесь какими-то изученными метриками регресии и сделайте выводы о качестве полученной модели, а также о том, какие признаки наиболее важны с точки зрения полученной модели.

Ваш ход:

The most important factors are DIS, LSTAT and RM. The least important - INDUS and AGE.

I tried to quickly visualize the dependencies of the predicted values from the x values (the picture looks tiny but can be easily enlarged by clicking twice). Here, we can see that RM or LSTAT indeed do look like they correlate with y variable!

So, our R^2 is 0.68, which is not bad, but MSE is 30 - not exactly perfect.

Задание 3. Реализация логистической регресии (суммарно 10 баллов)

Логистическая регрессия не очень сильно отличается от обычной линейной регрессии и используется в задах классификации. Так как здесь мы снова будем пользоваться градиентным спуском, то нужно определить функцию потерь и ее градиент. Одним из самых популярных вариантов в задаче бинарной классификации является бинарная кросс-энтропия (BCE).

$$\mathcal L_{BCE}(y, \hat y) = -\sum_i \left[y_i\log\sigma(\hat y_i) + (1-y_i)\log(1-\sigma(\hat y_i))\right].$$

где $y$ это таргет желаемого результата и $\hat y$ является выходом модели. $\sigma$ - это логистическая функция, который преобразует действительное число $\mathbb R$ в вероятность $[0,1]$.

Единственная проблема данной функции это возможность получить 0 под знаком логарифма, что не очень хорошо. Попробуем справить с этим "в лоб". Скажем, что наши предсказания могут принимать значения от 0 + eps до 1 - eps, где eps очень маленькое число.

Задание 3.1. Реализация сигмоиды (0.5 баллов)

Реализуйте функцию sigmoid, которая переводит действительное число $\mathbb R$ в вероятность $[0,1]$.

Задание 3.2. BCE Loss и ее градиент (2.5 балла)

Так как мы с вами только начинаем изучать машинное обучение, то было бы слишком жестоко просить вас вычислить градиент BCE Loss (он не так сложен, просто нужно привыкнуть). Поэтому сразу напишем формулу для него:

$$ \nabla{\mathcal L_{BCE}(y, \hat y), X} = X^T (\sigma({\hat{y}}) - y) $$

Задание 3.3. Предсказания логистической регрессии (2 балла)

Реализуйте метод predict у класса CustomLogisticRegression, не забудьте про свободный член!

Снова проверим работу алгоритма на простом примере

Проверьте качество работы модели при помощи известных вам метрик бинарной классификации.

Accuracy is 87 % - looks not bad!

Задание 3.4. Применение логистической регрессии (5 баллов)

Мы будем использовать данные по свойствам покемонов (https://www.kaggle.com/abcsds/pokemon). В данном задании вам необходимо сначала сделать краткий EDA (Посмотреть на данные и их распределения, а также посмотреть, как различные признаки связаны между собой и с целевой переменной (Legendary)).

We can see that data are not standartised, so we will need to do the standartisation.

Now we can look at the distribution of every feature, except for the name and number (they don't look exactly meaningful).

We can see that we have much less legendary guys than normal ones - should use it when splitting on train and test! But are features interconnectted somehow? We can visualize correlation matrix.

It seems that Generation and index are interconnected, also total correlates with HP, speed of atack and speed of defence. We can look at the plots of how these variables loook like against each other in the pandas_profiling repoprt, section "Interactions"

So let's use the dataset without the index number and name. We have to recode Type 1, Type 2 and legendary.

Pokemons can be of 18 different types. We can split both type 1 and type 2 into 18 different columns, but that will give us + 36 new features. It's likely they will not be very meaningful. Alternatively, we can turn each class into a number (from 0 to 18) - but that will infer that some classes are more alike than the others. In addition, almost half of the pokemons do not have Type 2 at all. I propose to get rid of the Type variable, recode Legendary in 1 - legendary and 0 - not legendary and standartize the train and test sets.

Мы будем предсказывать является ли покемон легендарным или нет. Замените логическое значение колонки на числовое (перекодировав на 0 и 1). Также подумайте, как в этом случае лучше закодировать категориальные признаки (может быть, лучше их просто выбросить?).

Разделите ваши данные на тестовую и тренировочную выборку.

Обучите модель LogisticRegression из sklearn.

Выведите метрики вашего классификатора:

  1. Нарисуйте confusion matrix.

  2. Изобразите ROC кривую и посчитайте площадь под ней.

  3. Скажите, какие признаки оказались наиболее важны для модели.

Confusion matrix:

Looks like most of the pokemons (216) are not legendary and predicted to be non-legendary!

ROC-curve

The square under the ROC-curve is 0.73 - ideally, would be one.

Looks like Speed of attack, Speed and Total are the most important features!

Задание 4. Расскажите о вашей любимой музыкальной группе (исполнителе) (0.5 балла)

Расскажите, как вы познакомились с этой группой и скиньте несколько наиболее любимых треков)

Одна из моих любимых групп - "Немного нервно". Они исполняют дрим-фолк и прочую странную фиготу, а познакомила меня с этой группой лучшая подруга, когда мы обе были ещё в школе, а главная исполнительница ведёт твиттер под ником "Грушновато" (https://twitter.com/nervno_nemnogo). Примеры их песен - в видео ниже.

Therapy time

Напишите здесь ваши впечатления о задании: было ли интересно, было ли слишком легко или наоборот сложно и тд. Также сюда можно написать свои идеи по улучшению заданий, а также предложить данные, на основе которых вы бы хотели построить следующие дз.

Ваши мысли:

Кажется, что второе задание гораздо приятнее, чем первое - я потратила на него всё утро и весь вечер воскресенья, но (надеюсь) что-то адекватное получилось, и почти всё даже более или менее понятно) Изначально очень запутывало употребление букв х, y и w всех по отношению к функции от w - может, лучше бы их было по-другому называть.

По датасету - было бы круто использовать какие-то более биологические данные, не про квартиры, а условно про транскриптомы или длины параподий - потому что покемоны это здорово, но пока непонятно, как именно это можно использовать)